package qq2564874169.tuaotuao.fx.orm.mybatis;


import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.TransactionIsolationLevel;
import qq2564874169.tuaotuao.fx.Dynamic;
import qq2564874169.tuaotuao.fx.FxAssert;
import qq2564874169.tuaotuao.fx.FxAutoCloseable;
import qq2564874169.tuaotuao.fx.orm.*;
import qq2564874169.tuaotuao.fx.orm.query.PageData;
import qq2564874169.tuaotuao.fx.orm.query.PageSet;

import java.util.*;

public abstract class AbstractMybatisSql extends AbstractSql implements MybatisSql {
    private SqlSession session;
    private MybatisContext context;
    private List<SqlSession> cursorSessionList = new ArrayList<>();
    private boolean inCursor;

    public AbstractMybatisSql(MybatisContext context) {
        this.context = context;
    }

    protected SqlSession getSession() {
        FxAssert.isFalse(inCursor, "当前连接对象正在游标中使用");
        return session != null ? session : getNewSession();
    }

    private SqlSession getNewSession() {
        return context.getNewSession(true);
    }

    protected Map<String, ?> createParamMap(SqlParam param) {
        if (param.isProc) {
            param.command = "call " + param.command + "(";
            if (!param.args.isEmpty()) {
                List<String> list = new ArrayList<>();
                for (String name : param.args.keySet()) {
                    list.add(placeholder(name));
                }
                param.command += String.join(",", list);
            }
            param.command += ")";
        }
        if (param.timeout != null) {
            context.setTimeout(param.timeout);
        }
        Map<String, Object> map = new HashMap<>(param.args);
        MybatisContext.setSql(map, param.command);
        return map;
    }

    @Override
    public final String paramName(String name) {
        return "#{" + name + "}";
    }

    @Override
    public void changeDataSource(String key) {

    }

    @Override
    protected int onExecute(SqlParam param) {
        SqlSession ss = getSession();
        try {
            Map<String, ?> map = createParamMap(param);
            return ss.getMapper(SqlProvider.class).execute(map);
        } finally {
            ss.clearCache();
            if (inTransaction() == false) {
                ss.close();
            }
        }
    }

    @Override
    protected <T> List<T> onQuery(Class<T> clazz, SqlParam param) {
        SqlSession ss = getSession();
        try {
            Map<String, ?> map = createParamMap(param);
            List<Map<String, Object>> list = ss.getMapper(SqlProvider.class).query(map);
            return param.convert(clazz, list);
        } finally {
            ss.clearCache();
            if (inTransaction() == false) {
                ss.close();
            }
        }
    }

    @Override
    protected MultipleReader onMultiple(SqlParam param) {
        SqlSession ss = getSession();
        try (FxAutoCloseable ignored = context.multipleSpace()) {
            Map<String, ?> map = createParamMap(param);
            List<List<Map<String, Object>>> list = ss.getMapper(SqlProvider.class).multiple(map);
            return new MybatisMultipleReader(list, param);
        } finally {
            ss.clearCache();
            if (inTransaction() == false) {
                ss.close();
            }
        }
    }

    @Override
    protected <T> CursorReader<T> onCursor(Class<T> clazz, SqlParam param, int fetchSize, boolean independent) {
        Map<String, ?> map = createParamMap(param);
        SqlSession ss = independent ? getNewSession() : getSession();
        Cursor<Map<String, Object>> cursor;
        try (FxAutoCloseable ac = context.useCursor(ss.getConnection(), fetchSize)) {
            cursor = ss.getMapper(SqlProvider.class).cursor(map);
            if (independent) {
                cursorSessionList.add(ss);
            }
        } catch (Exception e) {
            ss.close();
            throw e;
        }
        return new MybatisCursorReader<>(cursor) {
            private boolean ind = independent;
            private SqlSession css = ss;

            @Override
            public T next() {
                Map<String, Object> row = (Map<String, Object>) super.next();
                if (row == null) {
                    return null;
                }
                List<T> list = param.convert(clazz, List.of(row));
                return list.get(0);
            }

            @Override
            public void close() {
                super.close();
                css.clearCache();
                if (ind) {
                    css.close();
                    if (cursorSessionList != null) {
                        cursorSessionList.remove(css);
                    }
                    inCursor = false;
                } else if (inTransaction() == false) {
                    css.close();
                    inCursor = false;
                }
            }
        };
    }

    @Override
    protected void beginTransaction(TransactionLevel level) {
        FxAssert.isNull(session, "不能重复开启事务");
        if (level != null && TransactionLevel.Default != level) {
            switch (level) {
                case ReadUncommitted:
                    session = context.getNewSession(TransactionIsolationLevel.READ_UNCOMMITTED);
                    break;
                case ReadCommitted:
                    session = context.getNewSession(TransactionIsolationLevel.READ_COMMITTED);
                    break;
                case RepeatableRead:
                    session = context.getNewSession(TransactionIsolationLevel.REPEATABLE_READ);
                    break;
                case Serializable:
                    session = context.getNewSession(TransactionIsolationLevel.SERIALIZABLE);
                    break;
                default:
                    throw new RuntimeException("未实现的事务等级：" + level.name());
            }
        } else {
            session = context.getNewSession(false);
        }
    }

    @Override
    protected void commitTransaction() {
        if (session != null) {
            session.commit(true);
            session.close();
            session = null;
        }
    }

    @Override
    protected void rollbackTransaction() {
        if (session != null) {
            session.rollback(true);
            session.close();
            session = null;
        }
    }

    @Override
    public String placeholder(String name) {
        return "#{" + name + "}";
    }

    @Override
    public int stmtExecute(SqlParam param) {
        SqlSession ss = null;
        SqlBeforeArgs before = new SqlBeforeArgs(SqlOperateType.execute, param);
        try {
            onBefore(before);
            if (before.result == null) {
                ss = getSession();
                before.result = ss.update(param.command, param.args);
            }
            SqlAfterArgs after = before.toAfter();
            onAfter(after);
            return (int) after.result;
        } catch (Exception e) {
            onError(new SqlErrorArgs(before, e));
            throw e;
        } finally {
            if (ss != null) {
                ss.clearCache();
                if (inTransaction() == false) {
                    ss.close();
                }
            }
        }
    }

    @Override
    public <T> List<T> stmtQuery(Class<T> clazz, SqlParam param) {
        SqlSession ss = null;
        SqlBeforeArgs before = new SqlBeforeArgs(SqlOperateType.query, param);
        try {
            onBefore(before);
            if (before.result == null) {
                ss = getSession();
                boolean isDyRs = clazz == Dynamic.class;
                Class<?> rsClass = isDyRs ? Map.class : clazz;
                try (FxAutoCloseable ac = context.setResultType(rsClass)) {
                    List<?> items = ss.selectList(param.command, param.args);
                    if (isDyRs) {
                        before.result = Dynamic.asList(items);
                    } else {
                        before.result = items;
                    }
                }
            }
            SqlAfterArgs after = before.toAfter();
            onAfter(after);
            return (List<T>) after.result;
        } catch (Exception e) {
            onError(new SqlErrorArgs(before, e));
            throw e;
        } finally {
            if (ss != null) {
                ss.clearCache();
                if (inTransaction() == false) {
                    ss.close();
                }
            }
        }
    }

    @Override
    public MultipleReader stmtMultiple(SqlParam param) {
        SqlSession ss = null;
        SqlBeforeArgs before = new SqlBeforeArgs(SqlOperateType.multiple, param);
        try (FxAutoCloseable ignored = context.multipleSpace()) {
            onBefore(before);
            if (before.result == null) {
                ss = getSession();
                List<List<Map<String, Object>>> list = stmtQuery(param.command, param.args);
                before.result = new MybatisMultipleReader(list, param);
            }
            SqlAfterArgs after = before.toAfter();
            onAfter(after);
            return (MultipleReader) after.result;
        } catch (Exception e) {
            onError(new SqlErrorArgs(before, e));
            throw e;
        } finally {
            if (ss != null) {
                ss.clearCache();
                if (inTransaction() == false) {
                    ss.close();
                }
            }
        }
    }

    @Override
    public <T> PageData<T> pager(Class<T> clazz, PageSet pageSet, SqlParam param) {
        onPager(clazz, pageSet, param);
        try (MultipleReader reader = multiple(param)) {
            int count = reader.readOne(int.class);
            List<T> data = reader.read(clazz);
            return new PageData<>(pageSet, data, count);
        }
    }

    @Override
    public <T> PageData<T> stmtPager(Class<T> clazz, PageSet pageSet, SqlParam param) {
        SqlSession ss = null;
        SqlBeforeArgs before = new SqlBeforeArgs(SqlOperateType.multiple, param);
        try (FxAutoCloseable ignored = context.pageSpace(getPager(pageSet))) {
            onBefore(before);
            boolean isDyRs = clazz == Dynamic.class;
            Class<?> rsClass = isDyRs ? Map.class : clazz;
            if (before.result == null) {
                ss = getSession();
                try (FxAutoCloseable ac = context.setResultType(rsClass)) {
                    before.result = ss.selectList(param.command, param.args);
                }
            }
            SqlAfterArgs after = before.toAfter();
            onAfter(after);
            List<Object> list = (List<Object>) after.result;
            int count = ((List<Integer>) list.get(0)).get(0);
            if (isDyRs) {
                List<?> items = (List<?>) list.get(1);
                return new PageData<>(pageSet, (List<T>) Dynamic.asList(items), count);
            } else {
                List<T> items = (List<T>) list.get(1);
                return new PageData<>(pageSet, items, count);
            }
        } catch (Exception e) {
            onError(new SqlErrorArgs(before, e));
            throw e;
        } finally {
            if (ss != null) {
                ss.clearCache();
                if (inTransaction() == false) {
                    ss.close();
                }
            }
        }
    }

    @Override
    public <T> CursorReader<T> stmtCursor(Class<T> clazz, SqlParam param, int fetchSize, boolean independent) {
        SqlBeforeArgs before = new SqlBeforeArgs(SqlOperateType.cursor, param);
        try {
            onBefore(before);
            if (before.result == null) {
                SqlSession ss = independent ? getNewSession() : getSession();
                boolean isDy = clazz == Dynamic.class;
                Class<?> rsClass = isDy ? Map.class : clazz;
                Cursor<T> cursor;
                try (FxAutoCloseable rtAc = context.setResultType(rsClass);
                     FxAutoCloseable ac = context.useCursor(ss.getConnection(), fetchSize)) {
                    cursor = ss.selectCursor(param.command, param.args);
                    if (independent) {
                        cursorSessionList.add(ss);
                    } else {
                        inCursor = true;
                    }
                } catch (Exception e) {
                    ss.close();
                    throw e;
                }
                before.result = new MybatisCursorReader<T>(cursor) {
                    private boolean ind = independent;
                    private boolean cIsDyRs = isDy;
                    private SqlSession css = ss;

                    @Override
                    public T next() {
                        T data = super.next();
                        if (cIsDyRs && data != null) {
                            return (T) new Dynamic(data);
                        } else {
                            return data;
                        }
                    }

                    @Override
                    public void close() {
                        if (ind || inTransaction() == false) {
                            super.close();
                            css.clearCache();
                            css.close();
                            cursorSessionList.remove(css);
                        }
                    }
                };
            }
            SqlAfterArgs after = before.toAfter();
            onAfter(after);
            return (CursorReader<T>) after.result;
        } catch (Exception e) {
            onError(new SqlErrorArgs(before, e));
            throw e;
        }
    }

    @Override
    public boolean isClosed() {
        return context == null;
    }

    @Override
    public void close() {
        super.close();
        if (cursorSessionList != null) {
            for (SqlSession ss : cursorSessionList) {
                ss.clearCache();
                ss.close();
            }
            cursorSessionList.clear();
            cursorSessionList = null;
        }
        context = null;
    }
}
